package no.gnet.edvd.web.controller;

import java.io.BufferedReader;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.UnsupportedEncodingException;
import java.net.URI;
import java.net.URL;
import java.security.Key;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.UnrecoverableKeyException;
import java.security.cert.CertificateException;
import java.util.List;

import javax.crypto.Cipher;
import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec;
import javax.servlet.http.HttpServletRequest;

import no.gnet.edvd.DataStore;
import no.gnet.edvd.lightweight.NewUser;
import no.gnet.edvd.lightweight.SessionState;
import no.gnet.edvd.types.ActivityType;
import no.gnet.edvd.types.FacebookUser;
import no.gnet.edvd.types.Player;
import no.gnet.edvd.types.Transfer;
import no.gnet.edvd.types.Wallet;
import no.gnet.edvd.util.Mailer;
import no.gnet.edvd.util.Utilities;
import no.gnet.edvd.web.types.EdvdSessionUser;
import no.gnet.edvd.web.types.exception.EddeVeddeRuntimeException;
import no.gnet.edvd.web.types.exception.WrongPasswordException;
import no.gnet.edvd.web.util.Constants;

import org.apache.commons.codec.binary.Hex;
import org.apache.commons.validator.routines.EmailValidator;
import org.apache.log4j.Logger;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.ModelMap;
import org.springframework.util.Assert;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.servlet.view.RedirectView;

import com.restfb.DefaultFacebookClient;
import com.restfb.FacebookClient;
import com.restfb.types.User;

@Controller
public class SessionController extends CommonController{
	
	private String keypath = "/usr/edvd/edvd.jks";
	
	@Autowired
	DataStore datastore;
	
	@Autowired
	Mailer mailer;

	private static Logger logger = Logger.getLogger(SessionController.class);

	// creates the url for calling to oauth.
	public static String getAuthURL(String authCode) {
		String url = "https://graph.facebook.com/oauth/access_token?client_id="
				+ Constants.FacebookApiKey + "&redirect_uri="
				+ Constants.RedirURL + "&client_secret="
				+ Constants.FacebookApiSecret + "&code=" + authCode;
	
		return url;
	}

	public static String getMeURL(String accessToken) {
		String url = "https://graph.facebook.com/me?access_token="
				+ accessToken;
		return url;
	}
	
	
	
	@RequestMapping(value = "/api/session/loggedon", method = RequestMethod.GET)
	public @ResponseBody SessionState loggedOn(HttpServletRequest request) {
		EdvdSessionUser sessionUser = sessionUser(request);
		if(sessionUser==null){
			return new SessionState(false,null, null,null);
		}
		Player player = sessionUser.getPlayer();
		if(sessionDirty(request)){
			logger.info("Session dirty, reloading player.email " + player.email);
			setSessionUser(request, player.email, datastore);
		}
		Wallet wallet = datastore.getWalletForUser(player.id);
		boolean isloggedon = sessionUser!=null && player!=null;
		List<Transfer> unsettledTransfersForUser = datastore.getUnsettledTransfersForUser(player.id);
		SessionState sessionstate = new SessionState(isloggedon,player, wallet,unsettledTransfersForUser);
		logger.info("Returning sessionstate: "+sessionstate);
		return sessionstate;
	}	
	
	
	@RequestMapping(value = "/activateUser", method = RequestMethod.GET)
	public String activateUser(@RequestParam(value = "ac", required = true) String ac,HttpServletRequest request){
		String email=null;
		try {
			email = decryptWithAESKey(ac);
			Player player = new Player(email);
			Player splayer = datastore.getPlayer(player);
			if(splayer!=null){
				datastore.activatePlayer(splayer.id);
			}
		} catch (Exception e) {
			logger.error(e);
			throw new EddeVeddeRuntimeException("Failed activating player");
		}
		return "redirect:../#activatedOk";
	}
	
	@RequestMapping(value = "/registerNewUser", method = RequestMethod.POST)
	public @ResponseBody Player registerUser(@RequestBody NewUser newUser, HttpServletRequest request) {
		Player storedp = null;
		logger.debug("new user reg: " + newUser);
		checkInputOk(newUser);
		try {
			Player player = new Player(newUser.firstName, newUser.lastName,
					newUser.email);
			datastore.storePlayerWithPassword(player,
					Utilities.hashAndHexEncodePw(newUser.password));
//			datastore.createEmptyStartupWalletForUser(player);
			storedp = datastore.getPlayer(player);
			
			String activationpage = request.getRequestURL().toString().replace("/registerNewUser","/activateUser");
			
			if(mailer==null){
				logger.error("Mailer unavailable in context!!!!");
			} else {
			mailer.sendMail("EddeVedde", "gautesmaaland@gmail.com",
					"Welcome to EddeVedde",
					"Hi "+newUser.firstName+". We are happy to see that you signed up for EddeVedde!\n\nComplete your "
					+ "registration by verifying your e-mail address. Click the link to confirm:\n\n "
					+ activationpage+ "?ac="+actcode(player.email)+" \n\n"
					+ "\n\nBest regards, EddeVedde Team");
			}
			logger.warn("Stored: " + storedp);
			
		} catch(Exception e){
			logger.error(e);
			throw new EddeVeddeRuntimeException("Error occured in player registration. Message is " + e.getMessage());
		}
		return storedp;
	}

	private String actcode(String email) {
		try {
			return encrypttWithAESKey(email);
		} catch (Exception e) {
			logger.error(e);
			throw new EddeVeddeRuntimeException("Error occured in player registration. Message is " + e.getMessage());
		}
	}

	private void checkInputOk(NewUser newUser) {
		Assert.notNull(newUser, "newUser must be non-null");
		Assert.notNull(newUser.email, "newUser.email must be non-null");
		Assert.notNull(newUser.firstName, "newUser.firstname must be non-null");
		Assert.notNull(newUser.lastName, "newUser.lastname must be non-null");
		Assert.notNull(newUser.password, "newUser.password must be non-null");
		if(!EmailValidator.getInstance().isValid(newUser.email)){
			throw new EddeVeddeRuntimeException("Email " + newUser.email+" is not a valid email");
		}
		if(datastore.getPlayer(new Player(newUser.email))!=null){
			throw new EddeVeddeRuntimeException("This email is already a player");
		}
	}		
	
	

	public boolean checkPwForUser(String email, String pw) throws UnsupportedEncodingException, NoSuchAlgorithmException {
		String hashedpw = Utilities.hashAndHexEncodePw(pw);
		String realpw = datastore.getHashedPasswordForUser(email);
		logger.info("Yeay checking incoming hash: "+hashedpw+", real is: "+realpw);
		return realpw.equals(hashedpw);		
	}

	private String getAccessTokenFromCode(String code) throws Exception {
		String authUrl = getAuthURL(code);
		URL url = new URL(authUrl);
		URI uri = new URI(url.getProtocol(), url.getHost(), url.getPath(),
				url.getQuery(), null);
		String result = readURL(uri.toURL());
	
		String[] resultSplited = result.split("&");
	
		return resultSplited[0].split("=")[1];
	
	}

	@RequestMapping(value = "/login", method = RequestMethod.GET)
	public RedirectView login(
			ModelMap model,
			HttpServletRequest request,
			@RequestParam(value = "code", required = false) String fbcode,
			@RequestParam(value = "error_code", required = false) String error_code,
			@RequestParam(value = "error_message", required = false) String error_message) {
		try {
			String accessToken = getAccessTokenFromCode(fbcode);
			FacebookClient facebookClient = new DefaultFacebookClient(
					accessToken);
			User user = facebookClient.fetchObject("me", User.class);
			FacebookUser storedFBUser = datastore.getFBUser(user.getId(),
					user.getEmail());
			if (storedFBUser != null) {
				Player player = datastore.getPlayer(storedFBUser.player_id);
				List<Transfer> unsettledTransfersForUser = datastore.getUnsettledTransfersForUser(player.id);
				request.getSession().setAttribute(Constants.EDVDUSER, EdvdSessionUser
						.createSessionUser(user, player,true,unsettledTransfersForUser));		
				logActivity(datastore, ActivityType.LOGIN, "Logged in using Facebook", player);
			} else {
				firstVisit(user);
			}
	
		} catch (Exception e) {
			logger.error(e);
			return new RedirectView("authfail");
		}
		return new RedirectView("welcome");
	}

	@RequestMapping(value = "/lpw", method = RequestMethod.POST)
	public String loginpw(ModelMap model, HttpServletRequest request,
			@RequestParam(value = "email", required = true) String email,
			@RequestParam(value = "userpw", required = true) String pw) {
		try {

			assertLogonOk(email, pw);
			
			Player player = setSessionUser(request, email, datastore);	
			logActivity(datastore,ActivityType.LOGIN, "Logged in using password", player);
		} catch (Exception e) {
			logger.error(e);
			return "redirect:../#erro/message:"+e.getMessage();
		}
		
		return "redirect:../";
	}

	private void assertLogonOk(String email, String pw) throws UnsupportedEncodingException, NoSuchAlgorithmException,
			WrongPasswordException {
		if(pw==null || pw.length()<1 || pw.length()>26){
			throw new IllegalArgumentException("Illegal input");
		}
		if(email==null || email.length()<1 || email.length()>26){
			throw new IllegalArgumentException("Illegal input");
		}
		if(datastore.getPlayer(new Player(email)) == null){
			throw new IllegalArgumentException("No such user");
		}
		if(!checkPwForUser(email, pw)){
			throw new WrongPasswordException("Wrong password");
		}
		
		
	}
	

	private void logoffFacebook(User facebookUserdata) {
		// TODO Auto-generated method stub
		
	}

	@RequestMapping(value = "/logout", method = RequestMethod.GET)
	public RedirectView logout(ModelMap model, HttpServletRequest request){
		EdvdSessionUser user = (EdvdSessionUser) request.getSession().getAttribute(Constants.EDVDUSER);
		if(user!=null){
			logger.warn("Logging off user " + user);
			if(user.getFacebookUserdata()!=null && user.authenticated){
				logoffFacebook(user.getFacebookUserdata());
			}
		}
		request.getSession().invalidate();
		return new RedirectView("../");
	}

	// reads the url.
	private String readURL(URL url) throws IOException {
	
		InputStream is = url.openStream();
		InputStreamReader inStreamReader = new InputStreamReader(is);
		BufferedReader reader = new BufferedReader(inStreamReader);
		String s = "";
		while (is.read() != -1) {
			s = reader.readLine();
		}
		reader.close();
		return s;
	}

	private void firstVisit(User user) {
		logger.debug("firstVisit");
	}

	
	
	
	public String encrypttWithAESKey(String inputData) throws Exception{
	    Cipher cipher = Cipher.getInstance("AES");
	    cipher.init(Cipher.ENCRYPT_MODE, getKey());

	    byte[] encrypted = cipher.doFinal((inputData).getBytes("UTF-8"));
	    return Hex.encodeHexString(encrypted);
	}

	public String decryptWithAESKey(String inputData)
			throws Exception {
		byte[] data = Hex.decodeHex(inputData.toCharArray());
		Cipher cipher = Cipher.getInstance("AES");
		cipher.init(Cipher.DECRYPT_MODE, getKey());
		byte[] newData = cipher.doFinal(data);
		return new String(newData);

	}

	private SecretKey getKey() throws KeyStoreException, FileNotFoundException, IOException, NoSuchAlgorithmException,
			CertificateException, UnrecoverableKeyException {
		KeyStore keyStore = KeyStore.getInstance("JCEKS");
		FileInputStream stream = new FileInputStream(keypath);
		keyStore.load(stream, "mercedes".toCharArray());
		Key key = keyStore.getKey("edvdkey", "mercedes".toCharArray());			
		SecretKey secKey = new SecretKeySpec(key.getEncoded(), "AES");
		return secKey;
	}
	
	
}
